#!/usr/bin/perl

use strict;
use sigtrap qw(die INT); # for cleanup
use File::Basename;
use Getopt::Mixed;
use Config::IniFiles;
use DRSync;

my $JOB_FILE = '.drsync.jobs'; # default job file

#
# just in case
#
my @COPY_ARGV = @ARGV;

#
# to determine if another process is executing
#
my $pidfile = basename($0) . '.pid';

# // {{ begin options
Getopt::Mixed::init("j:s jobfile>j c via-cron>c d delete-statefile>d");

my @opts;
$Getopt::Mixed::badOption = sub { my ($pos,$option,$reason) = @_;
								  push @opts, $option;
								  return ("","","");
							  };

my %opt;

while (my ($option, $value, $pretty) = Getopt::Mixed::nextOption()) {
	next if !$option;
	$option =~ s/\W/_/g;
	$opt{$option} = $value || 1;
}
Getopt::Mixed::cleanup();
# // {{ end options

#
# cron-compatiblity switch
#
&write_pidfile()
	if $opt{'c'} == 1;


# // {{ begin default job
#
# generate a default job file if running with "-j"
# for the first time.
#
if (($opt{'j'} == 1) && (! -e $JOB_FILE)) {
	eval {
		&gendefjobs();
		print "Default job file: ./$JOB_FILE has been created.\n";
		print "Please edit it and run drsync.pl with the -j option\n";
	};
	die $@ if $@;
}
# // {{ end default job

#
# use the jobfile pointed to by -j=<jobfile>
# or use the default
$JOB_FILE = $opt{'j'}
    unless $opt{'j'} == 1;

die "Missing jobfile: $opt{'j'}\n" 
	if defined $opt{'j'} && ! -e $JOB_FILE;

# // {{ begin configuration directives
my @directives =  &getparams('default');
my @activejobs = ();

foreach my $directive (@directives) {
	my $setting = &getval('default', $directive);

	if ($directive eq 'misc') {
		# the substition accounts for "setting" formats such as
		# misc=--update -progress existing
		my @misc = grep {s/\s*//g; s/^(?:--?)?(.+)/--$1/;} split (/,|\s+/, $setting);
		push @ARGV, @misc if @misc;
	}
	elsif ($directive eq 'activejobs') {
		# comma or space delimited
		@activejobs = grep {s/\s*//g;} split (/,|\s+/, $setting);
	}
	else {
		# other settings don't have preceeding dashes
		$setting =~ s/^-+//;
		push @ARGV, "--$directive=$setting"
			if defined $setting;
	}
}
# // {{ end configuration directives

if (! defined $opt{'j'}) {
    #no jobs to run
	eval {
		drsync(@ARGV);
	};
	if ($@ =~ /SIGINT/i) {
		die "Error: $@, arglist: ", join (" ", @ARGV);
	}
	elsif ($@) {
		warn "Error: $@, arglist: ", join (" ", @ARGV);
	}
}
else {
    #load @ARGV with the options and call it for each job
	foreach my $activejob (@activejobs) {
		#
		# get state-file
		#
		my $state_file = &getval($activejob, 'state-file');
		my $tmpstatefile = $state_file; # capture the filename so we can delete it later
		$state_file =~ s/^(?:--?)?(.+)/--state-file=$1/;

		die "Please define a state-file for job# $activejob in the job filone"
			if $state_file eq "";

		if (-e $tmpstatefile && $opt{'d'} == 1) {
			unlink($tmpstatefile) 
				or warn "Couldn't unlink $tmpstatefile\n";

			print "Deleted state-file $tmpstatefile\n";
		}

		push @ARGV, $state_file;

		my $recursive = &getval($activejob, 'recursive');
		$recursive =~ s/^(?:--?)?(.+)/--recursive=$1/;
		push @ARGV, $recursive;

		my $rsyncopts = &getval($activejob, 'rsyncopts');
		# the substition accounts for "setting" formats such as
		# rsyncopts=--copy-links, -foo, bar OR rsyncopts=--copy-links -foo bar
		my @rsyncopts = grep {s/\s*//g; s/^(?:--?)?(.+)/--$1/;} split (/,|\s+/, $rsyncopts);
		push @ARGV, @rsyncopts if @rsyncopts;
		
		#
		# get src dir
		#
		my $src = &getval($activejob, 'src');
		my ($srcdir) = ($src =~ /([^:]*)$/);

		#
		# if source is "localhost:" or "" and src dir does not exist 
		#
		die "Missing source dir '$srcdir' in job: $activejob"
			if (($src =~ /localhost:/ || $src !~ /:/) && ! -d $srcdir);

		#
		# get dst dir
		#
		my $dst = &getval($activejob, 'dst');
		my ($dstdir) = ($dst =~ /([^:]*)$/);

		#
		# if destination is "localhost:" or "" and dst dir does not exist 
		#
		die "Missing destination dir '$dst' in job: $activejob"	
			if (($dst =~ /localhost:/ || $dst !~ /:/) && ! -d $dstdir);

		#print "\ncalling drsync with :\n", join ("\n", @ARGV, $srcdir, $dstdir);

		eval {
			drsync (@ARGV, $srcdir, $dstdir);
		};
		if ($@ =~ /SIGINT/i) {
			die "Error: $@, arglist: ", join (" ", @ARGV, $srcdir, $dstdir);
		}
		elsif ($@) {
			warn "Error: $@, arglist: ", join (" ", @ARGV, $srcdir, $dstdir);
		}
	} # foreach active job
} # if defined opt{j}




#utility functions

sub getval
{
    die unless (@_ == 2);
    my ($section, $param) = @_;

    my $cfg = new Config::IniFiles(-file => $JOB_FILE);
    my $retval = $cfg->val($section, $param);
    return $retval;
} # end getval


sub getsections
{
    my $cfg = new Config::IniFiles(-file => $JOB_FILE);
    my @retval = $cfg->Sections();
    return (wantarray ? @retval : "@retval");
} # end getsections


sub getparams
{
	my $section = shift;
    my $cfg = new Config::IniFiles(-file => $JOB_FILE);
    my @retval = $cfg->Parameters($section);
    return (wantarray ? @retval : "@retval");
} # end getparams

sub gendefjobs
{
	return if -e $JOB_FILE;


	my $conf;
	($conf = <<'END_CONF') =~ s/^\s+//gm;

	## uncomment the options you wish to 
	## make default.	
    [default]
	#rsync=rsync
	#rsh=ssh
	#bzip2=bzip2
	#gzip=gzip
    #verbose=2
    #misc= update, existing, progress, backup

	## uncomment and edit the following block
	## to enable job functionality
	## // {{
	#activejobs=job0, job1

	#[job0]
	#state-file=.job0.state.tar.bz2
	#recursive=yes
	#rsyncopts=--copy-links
	#src=.
	#dst=localhost:/backup

    #[job1]
    #state-file=.job1.state.tar.bz2
	#recursive=yes
	#rsyncopts=--copy-links
	#src=.
    #dst=remotehost:/remote/backup
	## // }}

END_CONF

    open(CF, "> $JOB_FILE") or die "Cannot open $JOB_FILE\n";
	print CF $conf;
    close(CF);
} # end gendefjobs




sub write_pidfile
{
	my $running = &is_running();

	my $msg = "pid file: $pidfile found. Aborting!\n" .
		"Another instance could be running\n";
	
	die $msg
		if $running == 1;

    #
    # first instance. so create a pid file.
    #
	open(PIDFILE, "> ./$pidfile");
	print PIDFILE getpgrp(0);
	close(PIDFILE);
} # end write_pidfile

sub is_running
{
	my $running = 0;
	
	my $mypid = getpgrp(0);

	return $running
		if ! -e "./$pidfile";

	open(PIDFILE, "./$pidfile") or die "$!\n";
	my $savedpid = <PIDFILE>;
	close(PIDFILE);
	
	#
	# the current process is the
	# the executing process
	#
	return $running
		if $mypid == $savedpid;

	open(PS_P, "/bin/ps aux |");
	while(<PS_P>) {
		chomp;
		my @psline = split /\s+/, $_;
		
		#
		# exists in process table and is
		# sleeping or running
		#
		if ($psline[1] == $savedpid && $psline[7] =~ /S|R/) {
			$running = 1;
			last;
		}
	}
	close(PS_P);

	return $running;
} # end is_running

END
{
	#
	# this block is guaranteed to 
	# be executed. so we can do the
	# clean-up here
	#
	my $mypid = getpgrp(0);
	open(PIDFILE, "< ./$pidfile");
	my $savedpid = <PIDFILE>;
	close(PIDFILE);
	
	#
	# delete only if this process
	# created this pid file
	#
	if ($mypid == $savedpid) {
		print "Removing pid file: ./$pidfile\n";
		unlink ("./$pidfile");
	}
} # END
